﻿#region The MIT License
// The MIT License
// 
// Copyright (c) 2009 octalforty studios
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;
using System.Xml;

using Autofac;
using Autofac.Core;

using NDesk.Options;

using octalforty.Chinchillin.Console.Compiler;
using octalforty.Chinchillin.Console.Compiler.SemanticModel;
using octalforty.Chinchillin.Console.Deployment;
//using octalforty.Chinchillin.Shared.Deployment.Agent;
using octalforty.Chinchillin.Console.Properties;
using octalforty.Chinchillin.Shared.Deployment;
using octalforty.Chinchillin.Shared.Deployment.Agent;
using octalforty.Chinchillin.Shared.Deployment.Metadata;
using octalforty.Chinchillin.Shared.Deployment.Resources;
using octalforty.Chinchillin.Shared.Deployment.Resources.Impl;

namespace octalforty.Chinchillin.Console
{
    class Program
    {
        private static Uri callbackUri;
        private static TraceEventType eventTypeFilter;

        static void Main(string[] args)
        {
            System.Console.WriteLine(Resources.CopyrightInformation, 
                Assembly.GetExecutingAssembly().GetName().Version.ToString(4), 
                ProductInfo.ReleaseStage);

            var optionSet = new OptionSet()
                {
                    
                };

            if(args.Length < 1)
                return;

            var cdmFile = args[0];
            var task = args.First(a => a.StartsWith("/t:")).Substring(3);

            if(args.Any(a => a.StartsWith("/f:")))
                eventTypeFilter = (TraceEventType)Enum.Parse(typeof(TraceEventType), args.First(a => a.StartsWith("/f:")).Substring(3));
            else
                eventTypeFilter = TraceEventType.Transfer; // Highest value possible

            DeploymentManifest deploymentManifest = null;

            try
            {
                deploymentManifest = new CdmCompiler(new CommandLineArgsPlaceholderResolver()).Compile(File.ReadAllText(cdmFile));
            }
            catch(Exception e)
            {
                System.Console.WriteLine(e.ToString());
                return;
            }

            var container = CreateContainer();

            /*System.Console.WriteLine("Connecting to '{0}' with callback address '{1}'", 
                container.Resolve<IEndpointAddressProvider>().EndpointAddress, callbackUri);*/

            //var agent = container.Resolve<IDeploymentAgentService>();

            //
            // For each server, determine a list of available actions
            //
            // This is not required, actually, unless we want to  display usage information.
            // Additionally, this might not work when there are servers which require different callback addresses
            // (like local, which does not need to have callback address and a remote one, which actually needs
            // port forwarded, etc.)
           /* var serverActions = new Dictionary<string, ActionInfo[]>();

            foreach(var svr in deploymentManifest.Servers)
            {
                System.Console.WriteLine("Inspecting '{0}'", svr.Address);

                //using(var ls = container.BeginLifetimeScope())
                {
                    var dasp = container.Resolve<IDasProvider>();

                    System.Console.WriteLine("Connecting to '{0}'", svr.Address);
                    using(var das = dasp.GetDeploymentAgent(svr.Address))
                    {
                        System.Console.WriteLine("Connected to '{0}'", svr.Address);

                        using(var ocs = new OperationContextScope(((DeploymentAgentServiceClient)das).InnerChannel))
                        {

                            System.Console.WriteLine("Retrieving supported actions");
                            var actions = das.GetSupportedActions();


                            System.Console.WriteLine("Retrieved {0} supported actions", actions.Length);

                            serverActions.Add(svr.Name, actions);
                        } // using
                    } // using
                }
            } // foreach
            */
            //
            // Go execute
            try
            {
                Execute(container, deploymentManifest, task);
            }
            catch(Exception e)
            {
                System.Console.WriteLine(e.ToString());
            }


            /*System.Console.WriteLine("Press whenever ready");
            System.Console.ReadLine();*/

            /*var actions = agent.GetSupportedActions().OrderBy(x => x.Namespace).ThenBy(x => x.Name);

            foreach(var action in actions)
            {
                System.Console.WriteLine("{0}:{1}\n\t{2}", action.Namespace, action.Name, action.Description);

                if(action.Arguments != null)
                    foreach(var argument in action.Arguments)
                    {
                        System.Console.WriteLine("\t{0}\n\t\t{1}", argument.Name, argument.Description);
                    } // foreach
            } // foreach*/

            /*if(args.Length < 1)
                return;

            var ai = new ActionInvocation { QualifiedName = args[0] };

            for(int i = 1; i < args.Length; ++i)
            {
                if(args[i].StartsWith("/"))
                    continue;
                
                var p = args[i].Split('=');
                ai.Arguments.Add(new ActionInvocationArgument(p[0] , p[1]));
            }

            agent.Execute(ai);*/
        }

        private static void Execute(IContainer container, DeploymentManifest deploymentManifest, string task)
        {
            var taskDefinition = deploymentManifest.GetTaskDefinition(task);
            var tasks = GetTasks(deploymentManifest, taskDefinition, new List<string>());

            Execute(container, deploymentManifest, tasks);
        }

        private static IEnumerable<TaskDefinition> GetTasks(DeploymentManifest deploymentManifest, TaskDefinition taskDefinition, IList<string> done)
        {
            var tasks = taskDefinition.DependsOn.Except(done);

            foreach(var task in tasks)
                foreach(var t in GetTasks(deploymentManifest, deploymentManifest.GetTaskDefinition(task), done))
                {
                    done.Add(task);
                    yield return t;
                } // foreach

            yield return taskDefinition;
        }

        private static void Execute(IContainer container, DeploymentManifest deploymentManifest, IEnumerable<TaskDefinition> tasks)
        {
            IList<TaskDefinition> executed = new List<TaskDefinition>();

            try
            {
                foreach(var task in tasks)
                {
                    System.Console.WriteLine("Commit phase. Executing '{0}''", task.Name);
                    executed.Add(task);

                    foreach(var commitAction in task.Commit)
                    {
                        var serverDefinition = deploymentManifest.GetServerDefinition(commitAction.ServerDesignator);

                        System.Console.WriteLine("\texecuting on server '{0}' at '{1}'", commitAction.ServerDesignator, serverDefinition.Address);
                        using(var deploymentAgent = container.Resolve<IDasProvider>().GetDeploymentAgent(serverDefinition.Address))
                        {
                            var actionInvocation = new ActionInvocation {
                                Name = commitAction.Name, 
                                EventTypeFilter = eventTypeFilter
                            };
                            var arguments = commitAction.Properties.Select(p => new ActionInvocationArgument(p.Key, p.Value));

                            actionInvocation.Arguments.AddRange(arguments);

                            System.Console.WriteLine("\tInvoking '{0}'", commitAction.Name);
                            deploymentAgent.Execute(actionInvocation);
                        } // using
                    } // foreach
                } // foreach
            }
            catch(Exception)
            {
                System.Console.WriteLine("Rollback phase");
                foreach(var rollbackAction in executed.Reverse().SelectMany(t => t.Rollback))
                {
                    System.Console.WriteLine("Invoking '{0}''", rollbackAction.Name);

                    var svr = deploymentManifest.GetServerDefinition(rollbackAction.ServerDesignator);
                    using(var deploymentAgent = container.Resolve<IDasProvider>().GetDeploymentAgent(svr.Address))
                    {

                        var actionInvocation = new ActionInvocation { Name = rollbackAction.Name, EventTypeFilter = eventTypeFilter };
                        actionInvocation.Arguments.AddRange(rollbackAction.Properties.Select(p => new ActionInvocationArgument(p.Key, p.Value)));

                        try
                        {
                            deploymentAgent.Execute(actionInvocation);
                        }
                        catch(Exception)
                        {
                        }
                    } // using
                } // foreach

                throw;
            }
        }

        private static IContainer CreateContainer()
        {
            var containerBuilder = new ContainerBuilder();

            containerBuilder.
                RegisterType<Sha1ChecksumProvider>().
                    As<IChecksumProvider>().
                    PropertiesAutowired();

            containerBuilder.
                RegisterType<FileSystemManager>().
                    As<IFileSystemManager>().
                    PropertiesAutowired();

            containerBuilder.
                RegisterType<FileSystemResourceProvider>().
                    As<IResourceProvider>().
                    InstancePerLifetimeScope().
                    PropertiesAutowired();
            
            containerBuilder.
                Register(c => new DeploymentCallback()).
                    As<IDeploymentCallback>().
                    InstancePerLifetimeScope().
                    PropertiesAutowired();

            containerBuilder.
                Register(c => new InstanceContext(c.Resolve<IDeploymentCallback>())).
                    As<InstanceContext>().
                    InstancePerDependency();

            containerBuilder.
                RegisterType<CommandLineEndpointAddressProvider>().
                    As<IEndpointAddressProvider>().
                    InstancePerDependency().
                    PropertiesAutowired();

            /*containerBuilder.
                Register(c => new DuplexChannelFactory<IDeploymentAgentService>(c.Resolve<InstanceContext>(), 
                    new WSDualHttpBinding(WSDualHttpSecurityMode.None), "http://192.168.0.98:8080/chinchillin"));*/

            /*containerBuilder.
                Register(c => c.Resolve<DuplexChannelFactory<IDeploymentAgentService>>().CreateChannel()).
                InstancePerLifetimeScope();*/

            
            //binding.Security.Message.ClientCredentialType = MessageCredentialType.
            containerBuilder.
                RegisterType<DasProvider>().
                    As<IDasProvider>().
                    SingleInstance();

            return containerBuilder.Build();
        }
    }

    public interface IDasProvider
    {
        DeploymentAgentServiceClient GetDeploymentAgent(string address);
    }

    public class DasProvider : IDasProvider
    {
        private readonly IComponentContext c;

        public DasProvider(IComponentContext componentContext)
        {
            this.c = componentContext;
        }

        public DeploymentAgentServiceClient GetDeploymentAgent(string address)
        {
            var host = Dns.GetHostByName(Dns.GetHostName());
            //var address = new WebClient().DownloadString("http://www.whatismyip.com/automation/n09230945.asp");
            
            var callbackUri = 
                Environment.GetCommandLineArgs().Any(a => a.ToUpperInvariant().StartsWith("/C:")) ?
                    new Uri("http://" + Environment.GetCommandLineArgs().First(a => a.ToUpperInvariant().StartsWith("/C:")).Substring(3)) : 
                    null;
            
            var binding = new WSDualHttpBinding(WSDualHttpSecurityMode.None)
                {
                    OpenTimeout = TimeSpan.FromMinutes(1),
                    CloseTimeout = TimeSpan.FromMinutes(1),
                    SendTimeout = TimeSpan.FromMinutes(60),
                    ReceiveTimeout = TimeSpan.FromMinutes(60),
                    MaxReceivedMessageSize = 16000000,
                    MaxBufferPoolSize = long.MaxValue,
                    ReaderQuotas = XmlDictionaryReaderQuotas.Max,
                    UseDefaultWebProxy = true,
                    BypassProxyOnLocal = false
                };

            if(callbackUri != null)
            {
                //callbackUri = new Uri(callbackUri, Guid.NewGuid().ToString("N"));
                binding.ClientBaseAddress = callbackUri;

                System.Console.WriteLine("Callback: {0}", callbackUri);
            } // if

            var endpointAddress = new StaticAddressProvider(address).EndpointAddress;
            System.Console.WriteLine("Endpoint: {0}", endpointAddress);

            var das = new DeploymentAgentServiceClient(c.Resolve<InstanceContext>(), binding, new EndpointAddress(endpointAddress));
            return das;
        }
    }
}
